Skip to content

Comments

Tweak lang-mustache factory (#92211)#4

Open
MitchLewis930 wants to merge 1 commit intopr_014_beforefrom
pr_014_after
Open

Tweak lang-mustache factory (#92211)#4
MitchLewis930 wants to merge 1 commit intopr_014_beforefrom
pr_014_after

Conversation

@MitchLewis930
Copy link

@MitchLewis930 MitchLewis930 commented Jan 29, 2026

PR_014

Summary by CodeRabbit

  • Bug Fixes
    • Watcher now validates action payloads to detect and prevent self-referential data structures, improving system stability and preventing unexpected behavior during watch execution. Invalid payload configurations are caught early, and enhanced error messages provide clearer feedback to help users identify and resolve issues.

✏️ Tip: You can customize this high-level summary in your review settings.

This commits makes a few very minor tweaks to our Mustache scripting capabilities.

1. Switches from `DefaultMustacheFactory` to `SafeMustacheFactory`
In the event that the security manager were disabled (or removed, as is threatened in subsequent JDK releases), Mustache's "partial template" (`{{>partial}}`) support is a security risk because it would allow reading from an arbitrary URL or file on disk. Switching to the "Safe" version and passing in an empty set into the parent constructor disallows using partial Mustache templates. This also switches from `Function<String, String>` to the `TemplateFunction` value for the built-in functions.

3. Minor internal optimization
This removes useless grouping for one of the built-in mustache functions, and removes the call to `CollectionUtils.ensureNoSelfReferences` used in `CustomReflectionObjectHandler`. This check during stringification should not be necessary because Mustache templates without "partial" support cannot be self-referencing.
@coderabbitai
Copy link

coderabbitai bot commented Jan 29, 2026

📝 Walkthrough

Walkthrough

The changes refactor the Mustache templating infrastructure in CustomMustacheFactory to inherit from SafeMustacheFactory instead of DefaultMustacheFactory, replace Function<String, String> return types with TemplateFunction, and update URL encoding. Concurrently, self-reference validation is moved from CustomReflectionObjectHandler to Payload.java, with corresponding test expectations updated.

Changes

Cohort / File(s) Summary
Mustache Factory Refactoring
modules/lang-mustache/src/main/java/org/elasticsearch/script/mustache/CustomMustacheFactory.java
Changed parent class from DefaultMustacheFactory to SafeMustacheFactory; updated all custom function methods (ToJsonCode, JoinerCode) to return TemplateFunction instead of Function<String, String>; adjusted CustomJoinerCode.PATTERN regex from non-capturing group to direct matching; changed URLEncoder to use StandardCharsets.UTF_8 directly instead of .name() method.
Self-Reference Validation Relocation
modules/lang-mustache/src/main/java/org/elasticsearch/script/mustache/CustomReflectionObjectHandler.java, x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/watcher/watch/Payload.java
Removed stringify override from CustomReflectionObjectHandler that performed self-reference checks; moved CollectionUtils.ensureNoSelfReferences validation to Payload.Simple constructor to validate watcher action payload at the source.
Test Updates
x-pack/plugin/watcher/qa/rest/build.gradle, x-pack/plugin/watcher/qa/rest/src/yamlRestTest/resources/rest-api-spec/test/painless/40_exception.yml
Added test skip for painless/40_exception in build.gradle; updated exception test to skip on version 8.6.99 and simplified assertions to expect "Failed to transform payload" with self-reference error reason instead of Mustache-specific exceptions.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 A rabbit hops through Mustache halls,
Self-references caught by Payload's calls,
SafeMustacheFactory leads the way,
TemplateFunction shines today!
No more circular hares will pass,
Validation moves closer to the class! 🌟

🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'Tweak lang-mustache factory' is vague and non-descriptive, using the term 'tweak' which conveys no meaningful information about the specific security or functional changes made. Consider a more specific title like 'Replace DefaultMustacheFactory with SafeMustacheFactory to restrict partial templates' to clearly communicate the primary security change.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In
`@modules/lang-mustache/src/main/java/org/elasticsearch/script/mustache/CustomMustacheFactory.java`:
- Line 149: The abstract method createFunction currently returns
mustache.TemplateFunction which causes lambda return values to be re-parsed as
Mustache templates; update the API so these lambdas are plain-text functions:
change the return type of createFunction to
java.util.function.Function<String,String> (or otherwise return a plain text
Function) and adjust implementations for ToJsonCode and JoinerCode to implement
Function<String,String> so their outputs are not re-parsed as templates; update
any call sites that expect TemplateFunction to accept Function<String,String>
and ensure ToJsonCode and JoinerCode produce safe literal strings (no mustache
re-parsing).
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 555aa78 and 34ddaa7.

📒 Files selected for processing (5)
  • modules/lang-mustache/src/main/java/org/elasticsearch/script/mustache/CustomMustacheFactory.java
  • modules/lang-mustache/src/main/java/org/elasticsearch/script/mustache/CustomReflectionObjectHandler.java
  • x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/watcher/watch/Payload.java
  • x-pack/plugin/watcher/qa/rest/build.gradle
  • x-pack/plugin/watcher/qa/rest/src/yamlRestTest/resources/rest-api-spec/test/painless/40_exception.yml
💤 Files with no reviewable changes (1)
  • modules/lang-mustache/src/main/java/org/elasticsearch/script/mustache/CustomReflectionObjectHandler.java
🧰 Additional context used
🧬 Code graph analysis (1)
x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/watcher/watch/Payload.java (1)
server/src/main/java/org/elasticsearch/common/util/CollectionUtils.java (1)
  • CollectionUtils (31-294)
🔇 Additional comments (6)
x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/watcher/watch/Payload.java (1)

40-42: Good guard against self-referential payloads.
Clear, targeted validation and a helpful message hint.

x-pack/plugin/watcher/qa/rest/build.gradle (1)

39-44: Skip entry aligns with updated exception source.

x-pack/plugin/watcher/qa/rest/src/yamlRestTest/resources/rest-api-spec/test/painless/40_exception.yml (1)

40-43: Updated skip and assertions correctly reflect the new failure path.

Also applies to: 82-83

modules/lang-mustache/src/main/java/org/elasticsearch/script/mustache/CustomMustacheFactory.java (3)

264-264: Sanity-check custom delimiter parsing after regex tweak

The simplification should be behavior-neutral; please confirm with a small test that join delimiter='...' still parses and rejects invalid variants as expected.


361-361: Java baseline confirmed as Java 17, which fully supports URLEncoder.encode(String, Charset). No action needed.


17-19: SafeMustacheFactory constructor correctly disables partial resolution

The implementation is sound. In mustache.java 0.9.10, the SafeMustacheFactory(Set<String> allowedResourceNames, String resourceRoot) constructor exists and works as intended: Collections.emptySet() as the whitelist means no template/partial names are allowed to load, effectively blocking all partial resolution from the filesystem or classpath regardless of the resource root parameter.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

}

protected abstract Function<String, String> createFunction(Object resolved);
protected abstract TemplateFunction createFunction(Object resolved);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

find . -name "CustomMustacheFactory.java" -type f

Repository: Signal65/elasticsearch-CodeRabbit

Length of output: 175


🏁 Script executed:

cat -n modules/lang-mustache/src/main/java/org/elasticsearch/script/mustache/CustomMustacheFactory.java | head -100

Repository: Signal65/elasticsearch-CodeRabbit

Length of output: 4362


🏁 Script executed:

cat -n modules/lang-mustache/src/main/java/org/elasticsearch/script/mustache/CustomMustacheFactory.java | sed -n '140,160p'

Repository: Signal65/elasticsearch-CodeRabbit

Length of output: 1042


🏁 Script executed:

cat -n modules/lang-mustache/src/main/java/org/elasticsearch/script/mustache/CustomMustacheFactory.java | sed -n '180,220p'

Repository: Signal65/elasticsearch-CodeRabbit

Length of output: 2028


🏁 Script executed:

cat -n modules/lang-mustache/src/main/java/org/elasticsearch/script/mustache/CustomMustacheFactory.java | sed -n '235,260p'

Repository: Signal65/elasticsearch-CodeRabbit

Length of output: 1100


🌐 Web query:

mustache.java 0.9.10 TemplateFunction interface documentation behavior

💡 Result:

In mustache.java 0.9.10, TemplateFunction is a marker interface for section lambdas that tells the engine to treat the lambda’s return value as Mustache source and compile/render it again (i.e., “reparse the results again”). [2]

This contrasts with using a plain Java 8 Function<String,String> for a section ({{#func}}...{{/func}}):

  • Function<String,String>: the section body is rendered first (post-substitution) and then passed to apply; the returned string is inserted as text (not recompiled as a Mustache template). [1]
  • TemplateFunction: the lambda is invoked in the pre-substitution mode; the returned string is treated as Mustache template text and is reparsed/re-rendered against the current context. [1], [2]

Practical implication: implement TemplateFunction when you want your lambda to generate Mustache tags (e.g., return "Hello {{name}}") and have those tags evaluated; use plain Function when you only want to transform already-rendered text. [1], [2]


🌐 Web query:

mustache.java TemplateFunction vs Function lambda section rendering escaping

💡 Result:

In mustache.java there are two “lambda section” styles, and they differ mainly in when rendering happens (which directly affects escaping):

Function<String,String> (Java 8 Function) = post-substitution

  • mustache.java renders the section body first (i.e., {{name}} etc. inside the section are already expanded/escaped), then passes the resulting text to your Function (“post-substitution”). (github.com)
  • The Function’s returned string is then written out as plain text (it is not re-parsed as a Mustache template). (github.com)
    Escaping implication: anything inside the section like {{name}} was already escaped by the normal rules before your function sees it; your function output isn’t “auto-escaped” afterward (it’s just output).

TemplateFunction = pre-substitution + reparse

  • TemplateFunction exists specifically so mustache.java will reparse (re-render) the function’s returned string as a Mustache template. (javadoc.io)
  • mustache.java describes this as “reparse the results … again” and contrasts it with Function post-substitution behavior. (github.com)
    Escaping implication: because the returned string is treated as a template, any {{var}} that appears in what you return will be rendered with normal Mustache escaping rules during that re-render (and {{{var}}} / {{& var}} would be unescaped per Mustache rules). (github.com)

If you tell me your exact template snippet + which type you’re using, I can spell out precisely what gets escaped at each step.

Citations:


Investigate whether TemplateFunction is the correct type for these lambdas

In mustache.java 0.9.10, TemplateFunction causes the lambda's returned string to be re-parsed as Mustache template source, whereas Function<String,String> treats the return value as plain text. This distinction is critical:

  • ToJsonCode (lines 190-214): Returns JSON-formatted strings that may contain {{ characters. If re-parsed as templates, literal JSON syntax could be misinterpreted as Mustache directives.
  • JoinerCode (lines 241-255): Returns concatenated strings. If delimiters or items contain {{, they would trigger template re-parsing unintentionally.

Verify that TemplateFunction is the intended type (not a regression from a prior Function<String,String> implementation) and confirm these lambdas do not produce output that would cause unintended re-parsing.

🤖 Prompt for AI Agents
In
`@modules/lang-mustache/src/main/java/org/elasticsearch/script/mustache/CustomMustacheFactory.java`
at line 149, The abstract method createFunction currently returns
mustache.TemplateFunction which causes lambda return values to be re-parsed as
Mustache templates; update the API so these lambdas are plain-text functions:
change the return type of createFunction to
java.util.function.Function<String,String> (or otherwise return a plain text
Function) and adjust implementations for ToJsonCode and JoinerCode to implement
Function<String,String> so their outputs are not re-parsed as templates; update
any call sites that expect TemplateFunction to accept Function<String,String>
and ensure ToJsonCode and JoinerCode produce safe literal strings (no mustache
re-parsing).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants